#include "FilledPolygons.h"

int FilledPolygons::WINDOW_WIDTH = 320;
int FilledPolygons::WINDOW_HEIGHT = 240;

FilledPolygons::FilledPolygons(void)
{
	/* Initialize the SDL library */
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
        cerr << "Couldn't initialize SDL: " << SDL_GetError() << endl;
    }

	const char* WINDOW_TITLE = "Sample Filled Polygons";

	/* Initialize the display, requesting a software surface */
    screen = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 0, SDL_HWSURFACE | SDL_DOUBLEBUF);
    if (screen == NULL)
	{
        cerr << "Couldn't set " << WINDOW_WIDTH << "x" << WINDOW_HEIGHT << " video mode: " << SDL_GetError() << endl;
    }
	else
	{
		SDL_WM_SetCaption(WINDOW_TITLE, 0);
	}
	polygon = NULL;
}

FilledPolygons::~FilledPolygons(void)
{
	if (polygon != NULL)
	{
		delete polygon;
	}
	SDL_FreeSurface(screen);
	SDL_Quit();
}

void FilledPolygons::run(void)
{
	bool quit = false;
	SDL_Event event;
	Uint32 yellow = SDL_MapRGB(screen->format, 0xff, 0xff, 0x00);
	Polygon::Point* newPoint;
	Polygon::Point* lastPoint;

	while (!quit)
	{
		while (SDL_PollEvent(&event))
		{
			switch (event.type)
			{
				case SDL_MOUSEBUTTONDOWN:
					{
						if (event.button.button == SDL_BUTTON_LEFT || event.button.button == SDL_BUTTON_MIDDLE)
						{
							if (polygon == NULL)
							{
								polygon = new Polygon();
							}
							if (!polygon->isClosed())
							{
								newPoint = new Polygon::Point(event.motion.x, event.motion.y);
								if (!polygon->isEmpty())
								{
									lastPoint = polygon->getLastPoint();
									polygon->add(newPoint);

									if (polygon->isClosed())
									{
										delete newPoint;
										newPoint = NULL;
										newPoint = polygon->getFirstPoint();
									}
									SDL_LockSurface(screen);
									drawLine(lastPoint->getX(), lastPoint->getY(), newPoint->getX(), newPoint->getY(), yellow);

									if (polygon->isClosed())
									{
										fillPolygon();
									}
									SDL_UnlockSurface(screen);
									SDL_UpdateRect(screen, 0, 0, 0, 0);
								}
								else
								{
									polygon->add(newPoint);
								}
							}
						}
						else
						{
							SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0, 0, 0));
							SDL_UpdateRect(screen, 0, 0, 0, 0);
							delete polygon;
							polygon = NULL;
						}
					}
					break;
				case SDL_KEYDOWN:
					{
						if (event.key.keysym.sym == SDLK_ESCAPE)
						{
							quit = true;
						}
					}
					break;
				case SDL_QUIT:
					quit = true;
					break;
			}
		}
		SDL_PumpEvents();
	}
}

/*
 * The method of rendering the line is to loop through the points in one dimension (x or y) and calculate the corresponding position in the other dimension,
 * using the line's slope. If there are more points in the x dimension (abs(xdiff)>abs(ydiff)), it loops through the points in the x dimension and calculate
 * the corresponding y values; otherwise, it loops through the points in the y dimension and calculate the corresponding x values.
 * Handling these two cases separately is necessary to prevent gaps in the line.
 */
void FilledPolygons::drawLine(int x1, int y1, int x2, int y2, Uint32 color)
{
	int xdiff = x2-x1;
	int ydiff = y2-y1;

	if (absoluteValue(xdiff) > absoluteValue(ydiff))
	{
		int xmin, xmax;
		if (x1 < x2)
		{
			xmin = x1;
            xmax = x2;
		}
		else
		{
			xmin = x2;
            xmax = x1;
		}
		float slope = (float)ydiff / (float)xdiff;
		float y = 0.0;
		for (float x = xmin; x <= xmax; x += 1.0f)
		{
			y = y1 + ((x - x1) * slope);
			putPixel(round(x), round(y), color);
		}
	}
	else
	{
		int ymin, ymax;
		if (y1 < y2)
		{
			ymin = y1;
            ymax = y2;
		}
		else
		{
			ymin = y2;
            ymax = y1;
		}
		float slope = (float)xdiff / (float)ydiff;
		float x = 0.0;
		for (float y = ymin; y <= ymax; y += 1.0f)
		{
			x = x1 + ((y - y1) * slope);
			putPixel(round(x), round(y), color);
		}
	}
}

int FilledPolygons::absoluteValue(int value)
{
	int newValue;
	if (value >= 0)
	{
		newValue = value;
	}
	else
	{
		newValue = -value;
	}
	return newValue;
}

int FilledPolygons::round(float value)
{
	return (int)(value+0.5);
}

/*
 * Set the pixel at (x, y) to the given value
 * NOTE: The surface must be locked before calling this!
 */
void FilledPolygons::putPixel(int x, int y, Uint32 pixel)
{
    int bpp = screen->format->BytesPerPixel;
    /* Here p is the address to the pixel we want to set */
    Uint8 *p = (Uint8*)screen->pixels + y * screen->pitch + x * bpp;

    switch(bpp)
	{
	case 1:
        *p = pixel;
        break;
    case 2:
        *(Uint16*)p = pixel;
        break;

    case 3:
        if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
		{
            p[0] = (pixel >> 16) & 0xff;
            p[1] = (pixel >> 8) & 0xff;
            p[2] = pixel & 0xff;
        }
		else
		{
            p[0] = pixel & 0xff;
            p[1] = (pixel >> 8) & 0xff;
            p[2] = (pixel >> 16) & 0xff;
        }
        break;
    case 4:
        *(Uint32*)p = pixel;
        break;
    }
}

void FilledPolygons::fillPolygon(void)
{
	vector<int> x_coordinates = polygon->getPointsXCoordinate();
	vector<int> y_coordinates = polygon->getPointsYCoordinate();

	vector<short> x;
	x.resize(polygon->getPointsCount());
	copy(x_coordinates.begin(), x_coordinates.end(), x.begin());

	vector<short> y;
	y.resize(polygon->getPointsCount());
	copy(y_coordinates.begin(), y_coordinates.end(), y.begin());

	filledPolygonRGBA(screen, &x[0], &y[0], polygon->getPointsCount(), 127, 127, 0, 150);
	/*int pixelX, pixelY;
	int nodes, i, j, swap;
	vector<int> polyX = polygon->getPointsXCoordinate();
	vector<int> polyY = polygon->getPointsYCoordinate();
	int nodeX[100];
	for (pixelY=0; pixelY<WINDOW_HEIGHT; pixelY++)
	{
		nodes = 0;
		j = polygon->getPointsCount()-1;
		for (i=0; i<polygon->getPointsCount(); i++)
		{
			if (((float)polyY[i])<(float)pixelY && ((float)polyY[j])>=(float)pixelY || ((float)polyY[j])<(float)pixelY && ((float)polyY[i])>=(float)pixelY)
			{
				nodeX[nodes++] = (int)(((float)polyX[i])+(pixelY-(float)polyY[i])/(((float)polyY[j])-(float)polyY[i])*(((float)polyX[j])-(float)polyX[i]));
				j=i;
			}
		}
		i=0;
		while (i<nodes-1)
		{
			if (nodeX[i]>nodeX[i+1])
			{
				swap = nodeX[i];
				nodeX[i]=nodeX[i+1];
				nodeX[i+1]=swap;
				if (i)
				{
					i--;
				}
			}
			else
			{
				i++;
			}
		}
		int IMG_RIGHT = 320;
		int IMG_LEFT = 0;
		Uint32 yellow = SDL_MapRGB(screen->format, 0xff, 0xff, 0x00);
		for (i=0; i<nodes; i+=2)
		{
			if (nodeX[i]>=IMG_RIGHT)
			{
				break;
			}
			if (nodeX[i+1]>IMG_LEFT)
			{
				if (nodeX[i]<IMG_LEFT)
				{
					nodeX[i] = IMG_LEFT;
				}
				if (nodeX[i+1]>IMG_RIGHT)
				{
					nodeX[i+1] = IMG_RIGHT;
				}
				for (j=nodeX[i]; j<nodeX[i+1]; j++)
				{
					putPixel(j, pixelY, yellow);
				}
			}
		}
	}*/
}

/* Polygon interface implementation */
FilledPolygons::Polygon::Polygon(void)
{
	_isClosed = false;
}

FilledPolygons::Polygon::~Polygon(void)
{
	list<Point*>::iterator it;
	for (it=points.begin(); it!=points.end(); it++)
	{
		delete (*it);
	}
	points.erase(points.begin(), points.end());
}

void FilledPolygons::Polygon::add(Point* newPoint)
{
	Point* firstPoint;
	if (points.size() > 1)
	{
		firstPoint = points.front();
		if (!newPoint->equals(firstPoint, 10))
		{
			points.push_back(newPoint);
		}
		else
		{
			_isClosed = true;
		}
	}
	else
	{
		points.push_back(newPoint);
	}
}

bool FilledPolygons::Polygon::isEmpty(void)
{
	return points.empty();
}

bool FilledPolygons::Polygon::isClosed(void)
{
	return _isClosed;
}

FilledPolygons::Polygon::Point* FilledPolygons::Polygon::getFirstPoint(void)
{
	return points.front();
}

FilledPolygons::Polygon::Point* FilledPolygons::Polygon::getLastPoint(void)
{
	return points.back();
}

list<FilledPolygons::Polygon::Point*> FilledPolygons::Polygon::getPoints(void)
{
	return points;
}

int FilledPolygons::Polygon::getPointsCount(void)
{
	return points.size();
}

vector<int> FilledPolygons::Polygon::getPointsXCoordinate(void)
{
	vector<int> x_coordinates;
	list<Point*>::const_iterator it;
	for (it=points.begin(); it!=points.end(); it++)
	{
		x_coordinates.push_back((*it)->getX());
	}
	return x_coordinates;
}

vector<int> FilledPolygons::Polygon::getPointsYCoordinate(void)
{
	vector<int> y_coordinates;
	list<Point*>::const_iterator it;
	for (it=points.begin(); it!=points.end(); it++)
	{
		y_coordinates.push_back((*it)->getY());
	}
	return y_coordinates;
}

/* Point interface implementation */
FilledPolygons::Polygon::Point::Point(int x, int y)
{
	xpos = x;
	ypos = y;
}

FilledPolygons::Polygon::Point::~Point(void)
{
}

int FilledPolygons::Polygon::Point::getX(void)
{
	return xpos;
}

int FilledPolygons::Polygon::Point::getY(void)
{
	return ypos;
}

/* Compares two points, analyzing if they are equal or similar depending on a threshold */
bool FilledPolygons::Polygon::Point::equals(Point* point, int diff)
{
	bool isSimilar = false;
	int xdiff = getX()-point->getX();
	int ydiff = getY()-point->getY();
	if (absoluteValue(xdiff)<diff && absoluteValue(ydiff)<diff)
	{
		isSimilar = true;
	}
	return isSimilar;
}

int FilledPolygons::Polygon::Point::absoluteValue(int value)
{
	int newValue;
	if (value >= 0)
	{
		newValue = value;
	}
	else
	{
		newValue = -value;
	}
	return newValue;
}
